package com.na.game.engine.data;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

import com.na.game.engine.GameManager.Clazz;
import com.na.game.engine.event.Updater;
import com.na.game.engine.resource.Destruction;
import com.na.game.engine.util.GameConfig;
import com.na.game.engine.util.Util;

/**
 * 数据格式
 * int size
 * for (int i = 0; i < size; i++) {
 * 		String key
 * 		byte type 0-int 1-float 2-String
 * 		T value
 * }
 * @author yang.li
 *
 */
public class DataManager implements Destruction, Updater, Clazz {

	private static final String DB_NAME = "com.na.game.engine.userdata";
	
	private static final int TYPE_INT = 0;
	private static final int TYPE_FLOAT = 1;
	private static final int TYPE_STRING = 2;
	
	private static final int SAVE_INTERVAL = 5 * 60 * 1000;
	private long preAutoSaveTime = System.currentTimeMillis();
	private boolean changed;
	
	private Context ctx;
	private Map<String, Object> datas = new HashMap<String, Object>();
	
	private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
	
	public DataManager(Context ctx) {
		this.ctx = ctx;
		readFromAssets(ctx);
	}
	
	private void readFromAssets(Context ctx) {
		if (GameConfig.SDCARD != null) {
			DataInputStream dis = null;
			try {
				FileInputStream fis = new FileInputStream(Util.getExternalFile(GameConfig.RES_GAME_DATA));
				dis = new DataInputStream(fis);
				int size = dis.readInt();
				for (int i = 0; i < size; i++) {
					String key = dis.readUTF();
					byte type = dis.readByte();
					Object value = null;
					if (type == TYPE_INT) {
						value = dis.readInt();
					} else if (type == TYPE_FLOAT) {
						value = dis.readFloat();
					} else {
						value = dis.readUTF();
					}
					datas.put(key, value);
				}
			} catch (Exception e) {
				readFromSharedPreferences(ctx);
			} finally {
				if (dis != null)
					try {
						dis.close();
					} catch (IOException e) {
					}
			}
		} else {
			readFromSharedPreferences(ctx);
		}
	}
	
	private void readFromSharedPreferences(Context ctx) {
		SharedPreferences sp = ctx.getSharedPreferences(DB_NAME, Context.MODE_PRIVATE);
		Map<String, ?> all = sp.getAll();
		Iterator<?> itr = all.entrySet().iterator();
		while (itr.hasNext()) {
			Entry<String, ?> entry = (Entry<String, ?>) itr.next();
			String key = entry.getKey();
			Object value = entry.getValue();
			datas.put(key, value);
		}
	}
	
	private synchronized void save() {
		if (!changed) {
			return;
		}
		Editor editor = ctx.getSharedPreferences(DB_NAME, Context.MODE_PRIVATE).edit();
		File file = Util.getExternalFile(GameConfig.RES_GAME_DATA);
		DataOutputStream dos = null;
		if (file != null) {
			try {
				dos = new DataOutputStream(new FileOutputStream(file));
				dos.writeInt(datas.size());
			} catch (Exception e) {
			}
		}
		Iterator<Entry<String, Object>> itr = datas.entrySet().iterator();
		while (itr.hasNext()) {
			Entry<String, Object> entry = itr.next();
			String key = entry.getKey();
			Object value = entry.getValue();
			
			write(editor, key, value); // write to sharedpreferences
			write(dos, key, value); // write to file
		}
		editor.commit();
		try {
			if (dos != null) {
				dos.flush();
				dos.close();
			}
		} catch (Exception e) {}
		changed = false;
	}
	
	private void write(Editor editor, String key, Object value) {
		if (value instanceof Integer) {
			editor.putInt(key, (Integer) value);
		} else if (value instanceof Float) {
			editor.putFloat(key, (Float) value);
		} else {
			editor.putString(key, value.toString());
		}
	}
	
	private void write(DataOutputStream dos, String key, Object value) {
		try {
			if (dos == null) {
				return;
			}
			dos.writeUTF(key);
			if (value instanceof Integer) {
				dos.write(TYPE_INT);
				dos.writeInt((Integer) value);
			} else if (value instanceof Float) {
				dos.write(TYPE_FLOAT);
				dos.writeFloat((Float) value);
			} else {
				dos.write(TYPE_STRING);
				dos.writeUTF(value.toString());
			}
		} catch (Exception e) {
		}
	}
	
	@Override
	public void destroy() {
		save();
	}

	@Override
	public void update() {
		if (System.currentTimeMillis() - preAutoSaveTime >= SAVE_INTERVAL) {
			preAutoSaveTime = System.currentTimeMillis();
			EXECUTOR.execute(new Runnable() {
				@Override
				public void run() {
					save();
				}
			});
			
		}
	}
	
	public Object get(String key) {
		return datas.get(key);
	}
	
	public <V> V get(String key, V defaultValue) {
		Object value = get(key);
		if (value != null) {
			return (V) value;
		}
		return defaultValue;
	}
	
	public void put(String key, Object value) {
		if (value instanceof Integer || value instanceof Float || value instanceof String) {
			datas.put(key, value);
			changed = true;
		} else {
			throw new IllegalArgumentException("couldn't support this type: " + value.getClass());
		}
	}
	
	@Override
	public Class<?> getClazz() {
		return getClass();
	}

}
